import new
import os
import simplejson as json
import stomp
import time
import traceback
from threading import Thread
import tempfile


############
# This crude garbage corrects a bug in twisted
#   Orbited ticket: http://orbited.org/ticket/111
#   Twisted ticket: http://twistedmatrix.com/trac/ticket/2447
import twisted.web.http
twisted.web.http.HTTPChannel.setTimeout = lambda self, arg: None
twisted.web.http.HTTPChannel.resetTimeout = lambda self: None
############

def generate_orbited_config():
    _, path = tempfile.mkstemp(text=True)
    with open(path, 'w') as f:
        print >>f, '''\
[global]
session.ping_interval = 40
session.ping_timeout = 30

[access]
* -> localhost:%d


[listen]
stomp://:%d

[logging]
debug=STDERR
info=STDERR
access=STDERR
warn=STDERR
error=STDERR

#Don't enable debug by default
enabled.default=info,access,warn,error''' % (STOMP_PORT, STOMP_PORT)        
    return path

from twisted.internet import reactor
from twisted.web import resource, server, static
import orbited.config; 
import random
STOMP_PORT = random.randint(10000, 60000)
orbited.config.setup(options=new.classobj('',(),{'config':generate_orbited_config()}))
import orbited.logging
orbited.logging.setup(orbited.config.map)
import orbited.cometsession
import orbited.proxy
import morbid
import orbited.system
root = None
stomp_server = None
HTTP_PORT = 8080
class SelfHandler:
        def __init__(self, obj):            
            self.obj = obj
        def __getattr__(self,  mname):
            return getattr(self.obj, mname)
def register(obj, HandlerClass=SelfHandler):  
    global stomp_server
    if not stomp_server:
        stomp_server = StompServer()  
    stomp_server.add_obj(obj, HandlerClass)
def set_root(root_path):    
    global root
    #static files are under /static
    root = static.File(root_path)
    root.putChild('orbited', static.File('third_party/orbited/static'))
    root.putChild('js', static.File('common/js'))
    root.putChild('css', static.File('common/css'))
    #add handler to retrieve STOMP_PORT      
    class StompPage(resource.Resource):
        isLeaf = True
        def render_GET(self, request):
            return "%d" % STOMP_PORT
    root.putChild('stomp-port', StompPage())

def add_static(path, url = None):
    global root    
    if not os.path.exists(path):
        raise Exception("path %s not found" % path) 
    root.putChild(url or path, static.File(path))
def listen(http_port):
    global root, HTTP_PORT
    #everything under /tcp (no matter what the port is) will be handled by comet transport
    reactor.listenWith(orbited.cometsession.Port, factory=orbited.proxy.ProxyFactory(), resource=root, childName='tcp')    
    #http
    reactor.listenTCP(http_port, server.Site(root), interface='')
    #stomp
    reactor.listenTCP(STOMP_PORT, morbid.get_stomp_factory(""), interface='') 
    HTTP_PORT = http_port
    
 
def run():    
    reactor.callLater(0.3, start_msg)
    reactor.callLater(1, stomp_server.run)
    reactor.run()

def start_msg():
    print "Please go to http://localhost:%d for a web interface" % HTTP_PORT

def stop():    
    reactor.stop()

class StompServer():
    _stop = False
    objs = {}   
    handlers = {}    
    def add_obj(self, obj, handlerClass):
        if not hasattr(handlerClass, 'web_publish'):
            raise Exception('class %s must define a web_publish method' % handlerClass)
        key = str(id(obj)) if self.objs else 'first'
        self.objs[key] = obj
        self.handlers[key] = handlerClass(obj) 
    def stop(self):
        self._stop = True
    def on_error(self, headers, message):
        print 'received an error %s' % message
    def on_message(self, headers, message):
        if message == 'ABC123':
            print "test message received, connection successful"
            self.conn.unsubscribe(destination='/test')
            return
        print 'received a message %s' % message
        msg = None
        try:        
            msg = json.loads(message)
        except:
            print "message %s can not be parsed from json " % message[:30]
            return
        if 'id' not in msg:
            print "id field of the message is not set"
            return
        if msg['id'] not in self.objs:
            print "no object with id ", msg['id'], " found"
            return            
        obj = self.objs[msg['id']]
        handler = self.handlers[msg['id']]
        mthd = str(msg['method'])
        args = self.fix_arg_type(msg['args'])                        
        mthd = getattr(handler, mthd)
        mthd(**args)
    def send(self, msg, channel):
        serialized = None
        try:
            serialized = json.dumps(msg)
        except:
            print "message is not json serializable msg = ",msg
            return
        
        if '\n' in serialized:
            print "WARNING the serialized version contains new line"
            print "serialized =",serialized
        
        self.conn.send(serialized, destination='/client/%s' % channel)        
    
    
    def connect(self):
        conn = stomp.Connection(host_and_ports = [('localhost', STOMP_PORT)])
        conn.set_listener('', self)
        conn.start()
        print 'calling conn.connect()'
        conn.connect()
        print 'subscribing....'
        conn.subscribe(destination='/server', ack='auto')
        self.conn = conn
        self.test()
                
    def test(self):
        print 'testing the connection...'
        self.conn.subscribe(destination='/test', ack='auto')
        self.conn.send('ABC123',destination='/test')
        
    def run(self):        
        if not hasattr(self, 'conn') or not self.conn.is_connected():
            self.connect()            
        for objid, handler in self.handlers.iteritems():            
            self.send(handler.web_publish(), str(objid))
        reactor.callLater(2, self.run)
                                                   
                                
    def fix_arg_type(self,args):
        m = {}
        for key, val in args.iteritems():                        
            m[str(key)] = str(val)
        return m 